package xyz.xtt.asset.utils;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

/**
 * @author dale
 * @since 2024/12/30
 **/
@Component
public class RedisLockUtil {
    // 释放锁Lua脚本：仅当锁的值匹配时才删除键,避免极端情况下删除其他线程添加的锁
    private static final String RELEASE_LOCK_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "   return redis.call('del', KEYS[1]) " +
                    "else " +
                    "   return 0 " +
                    "end";
    // 资产交易记录订单锁key格式
    public static final String LOCK_KEY_USER_PURCHASE = "nft:lock:user:purchase:%s:%s";
    public static final String LOCK_KEY_USER_BENEFIT_PACKAGE_PURCHASE = "nft:lock:user:benefit:package:purchase:%d:%d";
    public static final String LOCK_KEY_PAY_ORDER = "nft:lock:pay:order:%d";
    public static final String LOCK_KEY_PAY_ORDER_UPDATE_ACCOUNT = "nft:lock:pay:order:update:account:%d";

    private final StringRedisTemplate redisTemplate;

    public RedisLockUtil(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 尝试获取Redis分布式锁(在timeout时间内尝试多次)
     *
     * @param lockKey          锁key
     * @param lockValue        锁value
     * @param autoReleaseTime  自动释放时间
     * @param autoReleaseTimeUnit 自动释放时间单位
     * @param timeout          超时时间
     * @param timeoutTimeUnit  超时时间单位
     * @return
     */
    public boolean tryLock(String lockKey, String lockValue, int autoReleaseTime, TimeUnit autoReleaseTimeUnit, int timeout, TimeUnit timeoutTimeUnit) {
        if (lockKey == null || lockValue == null || autoReleaseTime <= 0 || timeout <= 0){
            throw new IllegalArgumentException("try lock failed.");
        }
        long endTime = System.currentTimeMillis() + timeoutTimeUnit.toMillis(timeout);
        while (System.currentTimeMillis() < endTime) {
            if(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, autoReleaseTime, autoReleaseTimeUnit)){
                return true;
            }
            try {
                Thread.sleep(ThreadLocalRandom.current().nextInt(50, 150));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
        return false;
    }

    /**
     * 获取Redis分布式锁
     *
     * @param lockKey          锁key
     * @param lockValue        锁value
     * @param autoReleaseTime  自动释放时间
     * @param autoReleaseTimeUnit 自动释放时间单位
     * @return 是否成功获取锁
     */
    public boolean acquireLock(String lockKey, String lockValue, int autoReleaseTime, TimeUnit autoReleaseTimeUnit) {
        if (lockKey == null || lockValue == null || autoReleaseTime <= 0){
            throw new IllegalArgumentException("try lock failed.");
        }
        return redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, autoReleaseTime, autoReleaseTimeUnit);
    }


    /**
     * 使用 Lua 脚本释放 Redis 锁
     *
     * @param lockKey   锁的键
     * @param lockValue 锁内容
     * @return true 表示成功释放锁，false 表示释放锁失败
     */
    public boolean releaseLock(String lockKey, String lockValue) {
        RedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_SCRIPT, Long.class);
        // 执行 Lua 脚本
        Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue);
        return result != null && result == 1;
    }

}
